﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;

using Effects.GuiParts.Resources;

namespace Effects.Effects.GuiParts
{
    /// <summary>
    ///  Graphic control that represents potentiometer.
    /// </summary>
    /// <remarks>
    ///  <para>
    ///   This is the base class for all pots. Different types of pots can be iplemented by deriving this class (logarithmic, exponential, etc..)
    ///  </para>
    /// </remarks>
    public partial class Pot : UserControl
    {
        #region constructor 

        /// <summary>
        ///  Cretes pot with default look.
        /// </summary>
        public Pot()
        {
            this.angle = 3 * Math.PI / 2;
            InitializeComponent();
            this.Knob = PotKnob.SimpleKnobBig;

            RefreshInvoker = new MethodInvoker(Refresh);
            
            this.BackColor = Color.Transparent;
            this.SetStyle(ControlStyles.AllPaintingInWmPaint |
                          ControlStyles.UserPaint |
                          ControlStyles.DoubleBuffer, true);
        }

        #endregion

        #region properties

        /// <summary>
        ///  Minimum value of the pot. It is set when pot is fully left.
        /// </summary>
        protected double minValue;

        /// <summary>
        ///  Gets or sets minimum value of the potentiometer.
        /// </summary>
        public double MinValue
        {
            get { return minValue; }
            set
            {
                minValue = value;
                range = maxValue - minValue;
            }
        }

        /// <summary>
        ///  Maximum value of the pot. It is set when pot is fully right.
        /// </summary>
        protected Double maxValue;

        /// <summary>
        ///  Gets or sets maximum value of the potentiometer.
        /// </summary>
        public double MaxValue
        {
            get { return maxValue; }
            set
            {
                maxValue = value;
                range = maxValue - minValue;
            }
        }

        /// <summary>
        ///  Range between minimum and maximum values.
        /// </summary>
        protected double range;

        /// <summary>
        ///  Current value of potentiometer
        /// </summary>
        protected double value;

        /// <summary>
        ///  Current appearance of the potentiometer.
        /// </summary>
        protected PotKnob knob;

        /// <summary>
        ///  Gets or sets the knob (appearance of the pot)
        /// </summary>
        public PotKnob Knob
        {
            get { return knob; }
            set
            {
                knob = value;
                NewLook();
            }
        }

        #endregion

        #region main_work
        // reacitng on users actions

        #region mouse_commands

        /// <summary>
        ///  Control variable for adjusting the pot. Signalize whether the mouse button is pressed. It's value is changed
        ///  in OnMouseDown and OnMouseUp methods.
        /// </summary>
        private bool mouseDown;

        /// <summary>
        ///  Overridden OnMouseDown method. 
        /// </summary>
        protected override void OnMouseDown(MouseEventArgs mevent)
        {
            base.OnMouseDown(mevent);
            mouseDown = true;
        }

        /// <summary>
        ///  Overridden OnMouseUp method.
        /// </summary>
        protected override void OnMouseUp(MouseEventArgs mevent)
        {
            base.OnMouseUp(mevent);
            mouseDown = false;
        }

        /// <summary>
        ///  Overridden OnMouseMoveMethod. Reacts on user actions
        /// </summary>
        /// <remarks>
        ///  <para>
        ///   This method is responsible for keeping value and look coresponding. 
        ///  </para>
        /// </remarks>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            if (mouseDown)
            {
                // mouse is down
                Point center = new Point(this.Width / 2, this.Height / 2);

                // reversed Y coordinate, Y-axis is human readable
                Point p = new Point(e.Location.X - center.X, center.Y - e.Location.Y);

                // human like angle
                double ang = Math.PI / 4 + Math.Atan2(p.Y, p.X);
                if (ang < 0)
                    ang += 2 * Math.PI;
                if (ang > 2 * Math.PI)
                    ang -= 2 * Math.PI;

                if (ang >= minAngle && ang <= maxAngle)
                {
                    SetPosition((maxAngle - ang) / angleRange);
                    PotMove((maxAngle - ang) / angleRange);
                    Render();
                    this.Refresh();
                }
            }
        }

        #endregion

        #region angles
        // defines min and max angle that pot can be turned

        /// <summary>
        ///  Minimum show angle
        /// </summary>
        protected const double showMinAngle = 3 * Math.PI / 4;

        /// <summary>
        ///  Maximum show angle
        /// </summary>
        protected const double showMaxAngle = Math.PI / 4;

        /// <summary>
        ///  Show angle range
        /// </summary>
        protected const double showAngleRange = 3 * Math.PI / 2;

        /// <summary>
        ///  Help minimum angle for human-readable axis.
        /// </summary>
        protected const double minAngle = 0;

        /// <summary>
        ///  Help maximum angle for human-readable axis.
        /// </summary>
        protected const double maxAngle = 3 * Math.PI / 2;

        /// <summary>
        ///  Help angle range for human-readable axis.
        /// </summary>
        protected const double angleRange = 3 * Math.PI / 2;

        #endregion

        /// <summary>
        ///  Event raised when user moves the pot by the mouse. This Event is NOT raised when SetValue <see cref="E:Pot.Setvalue"/> method is called.
        /// </summary>
        public event PotValueChangedEventHandler ValueChanged;

        /// <summary>
        ///  Current angle of potentiometer.
        /// </summary>
        protected double angle;

        /// <summary>
        ///  Counts new angle according to value from [0;1] interval.
        /// </summary>
        /// <param name="position"> New position of the potenciometer </param>
        /// <remarks> 
        ///  There is linear depedency between given position and angle of the potentiometer 
        /// </remarks>
        protected virtual void PotMove(double position)
        {
            this.angle = showMinAngle + position * showAngleRange;
        }


        /// <summary>
        ///  Sets position of the pot and raises <see cref="E:Pot.ValueChanged"/> event. Accepted values are from range [0;1]
        /// </summary>
        /// <param name="value"> Position of the pot from [0;1] interval </param>
        /// <remarks>
        ///  <para>
        ///   Position of the potenciometer is given by value from interval [0;1]. Where 0 means minimum value and 1 maxumum value.
        ///   But mapping positions in between depends on the implementation of this method.
        ///  </para>
        /// </remarks>
        protected virtual void SetPosition(double value)
        {
            double newValue = value * range + minValue;
            if (newValue != this.value)
            {
                this.value = newValue;
                PotValueChangedEventArgs e = new PotValueChangedEventArgs(this.value);
                if (this.ValueChanged != null)
                    this.ValueChanged(this, e);
            }
        }

        /// <summary>
        ///  Sets pot's value to given one, but doesn't raise ValueChanged event
        /// </summary>
        /// <param name="value">value that should be set </param>
        /// <exception cref="ArgumentException">
        ///  ArgumentException is thrown when given value is greater than MaxValue, or less than Minvalue.
        /// </exception>
        public void SetValue(double value)
        {
            if (value < minValue)
                throw new ArgumentException("Value: " + value + " is less than minimum value: " + minValue);
            if (value > maxValue)
                throw new ArgumentException("Value: " + value + " is greater than maximmum value: " + maxValue);

            //angle = minAngle + (2*Math.PI + maxAngle - minAngle) * ((double)(value - minValue) / (maxValue - minValue));
            double position = (value - minValue) / range;
            PotMove(position);

            Render();
            RefreshMultiThread();
        }

        /// <summary>
        ///  Resets potentiometer.
        /// </summary>
        public void Reset()
        {
            //PotMove(3 * Math.PI / 2);
            SetPosition(0.5);
            PotMove(0.5);
            Render();
            Refresh();
        }

        #endregion 

        #region painting
        // painting and refreshing the component

        /// <summary>
        ///  Current look of potentiometer. This member is set acording to the Knob property.
        /// </summary>
        protected Image look;

        /// <summary>
        ///  Buffer for double buffering
        /// </summary>
        protected Bitmap buffer;

        /// <summary>
        ///  Method called when settings Knob property. <see cref="Pot.Knob"/> It preparses new look of potentiometer (loads image, sets size, etc...)
        /// </summary>
        protected void NewLook()
        {
            switch (this.knob)
            {
                case PotKnob.ChickenKnob:
                    this.look = Image.FromStream(new System.IO.MemoryStream(global::Effects.GuiParts.Resources.PotKnob.ChickenKnob));
                    break;
                case PotKnob.ChickenKnobMiddle:
                    this.look = Image.FromStream(new System.IO.MemoryStream(global::Effects.GuiParts.Resources.PotKnob.ChickenKnobMiddle));
                    break;
                case PotKnob.ChickenKnobSmall:
                    this.look = Image.FromStream(new System.IO.MemoryStream(global::Effects.GuiParts.Resources.PotKnob.ChickenKnobSmall));
                    break;
                case PotKnob.ChickenKnobTiny:
                    this.look = Image.FromStream(new System.IO.MemoryStream(global::Effects.GuiParts.Resources.PotKnob.ChickenKnobTiny));
                    break;
                case PotKnob.SimpleKnobBig:
                    this.look = Image.FromStream(new System.IO.MemoryStream(global::Effects.GuiParts.Resources.PotKnob.SimpleKnobBig));
                    break;
                case PotKnob.SimpleKnobMiddle:
                    this.look = Image.FromStream(new System.IO.MemoryStream(global::Effects.GuiParts.Resources.PotKnob.SimpleKnobMiddle));
                    break;
                case PotKnob.SimpleKnobSmall:
                    this.look = Image.FromStream(new System.IO.MemoryStream(global::Effects.GuiParts.Resources.PotKnob.SimpleKnobSmall));
                    break;
                case PotKnob.SimpleKnobTiny:
                    this.look = Image.FromStream(new System.IO.MemoryStream(global::Effects.GuiParts.Resources.PotKnob.SimpleKnobTiny));
                    break;
                case PotKnob.SpiderKnobBig:
                    this.look = Image.FromStream(new System.IO.MemoryStream(global::Effects.GuiParts.Resources.PotKnob.SpiderKnobBig));
                    break;
                case PotKnob.SpiderKnobSmall:
                    this.look = Image.FromStream(new System.IO.MemoryStream(global::Effects.GuiParts.Resources.PotKnob.SpiderKnobSmall));
                    break;
                case PotKnob.SpiderKnobTiny:
                    this.look = Image.FromStream(new System.IO.MemoryStream(global::Effects.GuiParts.Resources.PotKnob.SpiderKnobTiny));
                    break;
            }
            this.Size = look.Size;
            // update region 
            System.Drawing.Drawing2D.GraphicsPath path = new GraphicsPath();
            path.AddEllipse(0, 0, look.Size.Width, look.Size.Height);
            this.Region = new Region(path);
            Render();
            Refresh();
        }

        /// <summary>
        ///  Renders image that is displayed in OnPaint method <see cref="Pot.OnPaint"/>
        /// </summary>
        protected void Render()
        {
            //buffer = new Bitmap(this.Width, this.Height);
            Bitmap newBuffer = new Bitmap(this.Width, this.Height);
            Graphics g = Graphics.FromImage(newBuffer);

            // using rotate transform
            Matrix rotate = new Matrix();
            rotate.RotateAt(Convert.ToSingle((angle - 3 * Math.PI / 2) * 360 / (2 * Math.PI)), new PointF(this.Width / 2, this.Height / 2));

            g.Transform = rotate;
            g.SmoothingMode = SmoothingMode.HighQuality;
            g.DrawImage(look, new Point((this.Width - look.Width) / 2, (this.Height - look.Height) / 2));

            buffer = newBuffer;
        }

        /// <summary>
        ///  Overridden OnPaint method. Control is double buffered so it just paints previously rendered image.
        /// </summary>
        /// <param name="e"> Paint event arguments </param>
        protected override void OnPaint(PaintEventArgs e)
        {
            //base.OnPaint(e);
            try
            {
                Graphics g = e.Graphics;
                if (buffer == null)
                {
                    this.angle = 3 * Math.PI / 2;
                    Render();
                }
                g.DrawImage(buffer, new Point(0, 0));
            }
            catch (InvalidOperationException)
            {
                Invalidate();
            }
        }

        /// <summary>
        ///  Method Invoker 
        /// </summary>
        protected MethodInvoker RefreshInvoker;

        /// <summary>
        ///  Refresh method that can be called from not GUI threads.
        /// </summary>
        /// <remarks>
        ///  Quite common technique is used. Since .NET 2.0 Refresh <see cref="M:Pot.Refresh"/> method can be called only from thread that created the Control.
        ///  Therefore we have to invoke this method and not just call it.
        /// </remarks>
        protected void RefreshMultiThread()
        {
            if (InvokeRequired)
                BeginInvoke(RefreshInvoker);
            else
                Refresh();
        }

        /// <summary>
        ///  Overridden Onresize Method. It stops resizing. Size of control is defined byt it's Knob property
        /// </summary>
        /// <param name="e"> Event arguments </param>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            this.Size = look.Size;
        }

        #endregion
    }
  
}
